#region License, Terms and Conditions
//
// Jayrock - JSON and JSON-RPC for Microsoft .NET Framework and Mono
// Written by Atif Aziz (atif.aziz@skybow.com)
// Copyright (c) 2005 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Studio.Foundation.Json.Core.Conversion.Converters
{
    #region Imports

    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Text;
    using Studio.Foundation.Json.Core.Conversion;
    using System.Collections.Generic;

    #endregion

    public sealed class ArrayImporter : ImporterBase
    {
        public ArrayImporter() : this(null) { }

        public ArrayImporter(Type arrayType) :
            base(AssertArrayType(arrayType)) { }

        public List<Type> _baseTypes;

        public Type ElementType
        {
            get
            {
                return OutputType.IsGenericType ? OutputType.GetGenericArguments()[0] : OutputType.GetElementType();
            }
        }

        public static Type AssertArrayType(Type type)
        {
            if (type == null)
                return typeof(object[]);

            if (!type.IsGenericType)
            {
                if (!type.IsArray)
                    throw new ArgumentException(string.Format("{0} is not an array.", type.FullName), "arrayType");

                if (type.GetArrayRank() != 1)
                    throw new ArgumentException(string.Format("{0} is not one-dimension array. Multi-dimensional arrays are not supported.", type.FullName), "arrayType");
            }
            else
            {
                Debug.Assert(IsGenericArray(type));

                Debug.Assert(null != type.GetConstructor(Type.EmptyTypes));
            }

            return type;
        }

        /// <summary>
        /// Deprecated. We use Type.IsAssignableFrom() Instead. by marstone, 2009/03/24
        /// </summary>
        #region Deprecated Region
        /*
        public static List<Type> FindBaseGenericDefinitions(Type type)
        {

            List<Type> baseTypes = FindBaseTypes(type);
            List<Type> genericDefinitions = new List<Type>();
            foreach (Type baseType in baseTypes)
            {
                if (baseType.IsGenericType)
                {
                    if (baseType.IsGenericTypeDefinition)
                        genericDefinitions.Add(baseType);
                    else
                        genericDefinitions.Add(baseType.GetGenericTypeDefinition());
                }
            }
            return genericDefinitions;
        }


        /// <summary>
        /// Deprecated. We use Type.IsAssignableFrom() Instead.
        /// </summary>
        public static List<Type> FindBaseTypes(Type type)
        {
            List<Type> types = new List<Type>();
            // get base type
            List<Type> baseTypes = new List<Type>(type.GetInterfaces());
            if (null != type.BaseType) baseTypes.Add(type.BaseType);

            foreach (Type baseType in baseTypes)
            {
                if (0 > types.IndexOf(baseType))
                {
                    types.Add(baseType);
                    List<Type> baseBaseTypes = FindBaseTypes(baseType);
                    foreach (Type baseBaseType in baseBaseTypes)
                    {
                        if (0 > types.IndexOf(baseBaseType))
                            types.Add(baseBaseType);
                    }
                }
            }
            return types;
        }
        */

        #endregion

        protected override object ImportFromArray(ImportContext context, JsonReader reader)
        {
            Debug.Assert(context != null);
            Debug.Assert(reader != null);

            reader.Read();

            ArrayList list = new ArrayList();

            Type elementType = this.ElementType;

            while (reader.TokenClass != JsonTokenClass.EndArray)
                list.Add(context.Import(elementType, reader));

            object result = null;

            if (false == OutputType.IsGenericType)
                result = list.ToArray(elementType);
            else
            {
                // Create Generic Type
                result = Activator.CreateInstance(OutputType);

                Type[] args = OutputType.GetGenericArguments();
                bool isIList = IsDerived(typeof(IList), OutputType);
                bool isIListT = IsDerived(typeof(IList<>), OutputType);
                bool isICollectionT = IsDerived(typeof(ICollection<>), OutputType);

                // ICollection<>
                if (isICollectionT)
                {
                    foreach (object o in list)
                        OutputType.InvokeMember("Add", System.Reflection.BindingFlags.InvokeMethod, null, result, new object[] { o });
                }
                else if (isIList || isIListT)
                {
                    // IList<>, IList
                    int i = 0;
                    foreach (object o in list)
                        OutputType.InvokeMember("Insert", System.Reflection.BindingFlags.InvokeMethod, null, result, new object[] { i++, o });
                }
            }
            return ReadReturning(reader, result);
        }

        /// <summary>
        /// Can the type be handled?
        /// </summary>
        public static bool IsGenericArray(Type type)
        {
            if (false == type.IsGenericType) return false;
            if (type.IsGenericTypeDefinition) return false;

            Type[] args = type.GetGenericArguments();
            // Array must has ONLY one generice argument.
            if (args.Length != 1) return false;
            bool isIList = typeof(IList).IsAssignableFrom(type);
            bool isIListT = typeof(IList<>).MakeGenericType(args).IsAssignableFrom(type);
            bool isICollectionT = typeof(ICollection<>).MakeGenericType(args).IsAssignableFrom(type);

            return isICollectionT || isIList || isIListT;
        }

        public static bool IsDerived(Type ancestor, Type offspring)
        {
            if (ancestor.IsGenericTypeDefinition)
                if (offspring.IsGenericType && false == offspring.IsGenericTypeDefinition)
                    return ancestor.MakeGenericType(offspring.GetGenericArguments()).IsAssignableFrom(offspring);
            //
            return ancestor.IsAssignableFrom(offspring);
        }

        protected override object ImportFromBoolean(ImportContext context, JsonReader reader)
        {
            return ImportScalarAsArray(context, reader);
        }

        protected override object ImportFromNumber(ImportContext context, JsonReader reader)
        {
            return ImportScalarAsArray(context, reader);
        }

        protected override object ImportFromString(ImportContext context, JsonReader reader)
        {
            return ImportScalarAsArray(context, reader);
        }

        private object ImportScalarAsArray(ImportContext context, JsonReader reader)
        {
            Debug.Assert(context != null);
            Debug.Assert(reader != null);

            Type elementType = this.ElementType;
            Array array = Array.CreateInstance(elementType, 1);
            array.SetValue(context.Import(elementType, reader), 0);
            return array;
        }
    }
}